﻿using System.Linq;
using System.Text;

namespace RomanConvertKata
{
    public class RomanNumber
    {
        private const int MaxDuplicatedLimit = 4;

        public string RomanNumberString { get; }
        public int DecimalNumber { get; }

        public RomanNumber(string romanNumber)
        {
            RomanNumberString = romanNumber;
            DecimalNumber = ConvertRomanNumberToInt(romanNumber);
        }

        public RomanNumber(int decimalNumber)
        {
            DecimalNumber = decimalNumber;
            RomanNumberString = ConvertIntToRomanNumber(decimalNumber);
        }

        private static int ConvertRomanNumberToInt(string romanNumber)
        {
            var chars = romanNumber.ToCharArray();
            
            var preNumber = int.MaxValue;
            var sum = 0;

            foreach (var romanChar in chars)
            {
                var posNumber = AtomicRomanNumberChar.ConvertRomanCharToInt(romanChar);
                
                sum += CalculateAccumulate(posNumber, preNumber);

                preNumber = posNumber;
            }
            return sum;
        }

        private static int CalculateAccumulate(int posNumber, int preNumber)
        {
            return posNumber > preNumber ? MinusPreNumberFromPosNumber(posNumber, preNumber) : posNumber;
        }

        private static int MinusPreNumberFromPosNumber(int posNumber, int preNumber)
        {
            return posNumber - preNumber * 2;
        }

        private string ConvertIntToRomanNumber(int decimalNumber)
        {
            var numberBuilder = new StringBuilder();
            TryAppendAtomicRomanNumbersInDescendingOrder(decimalNumber, numberBuilder);
            return numberBuilder.ToString();
        }

        private static void TryAppendAtomicRomanNumbersInDescendingOrder(int decimalNumber, StringBuilder numberBuilder)
        {
            foreach (var atomicRomanNumber in AtomicRomanNumberChar.AtomicRomanNumbers.OrderByDescending(_=>_.DecimalNumber))
            {
                decimalNumber = TryAppendAtomicRomanNumber(decimalNumber, atomicRomanNumber, numberBuilder);
            }
        }

        private static int TryAppendAtomicRomanNumber(int remainDecimalNumber, AtomicRomanNumberChar atomicNumber, StringBuilder numberBuilder)
        {
            var atomicRomanNumber = atomicNumber.RomanNumberChar;

            remainDecimalNumber = AppendWhenNumberGreaterThanAtomicDecimalNumber(remainDecimalNumber, atomicNumber,
                numberBuilder, atomicRomanNumber);

            remainDecimalNumber = TryToAppendNumberInMinusStyle(remainDecimalNumber, atomicNumber, numberBuilder, atomicRomanNumber);
            return remainDecimalNumber;
        }

        private static int AppendWhenNumberGreaterThanAtomicDecimalNumber(int decimalNumber, AtomicRomanNumberChar atomicNumber,
            StringBuilder numberBuilder, char atomicRomanNumberChar)
        {
            var duplicatedCount = 0;
            while (decimalNumber >= atomicNumber.DecimalNumber)
            {
                numberBuilder.Append(atomicRomanNumberChar);
                decimalNumber -= atomicNumber.DecimalNumber;
                duplicatedCount++;
                if (DuplicatedGreaterThanLimit(duplicatedCount))
                {
                    RemoveDuplicatedPreNumber(numberBuilder);
                    var removedNumber = MaxDuplicatedLimit * atomicNumber.DecimalNumber;
                    ReAppendNumberInMinusStyle(atomicNumber, removedNumber, numberBuilder);
                    duplicatedCount = 0;
                }
            }
            return decimalNumber;
        }

        private static void ReAppendNumberInMinusStyle(AtomicRomanNumberChar atomicNumber, int targetNumber, StringBuilder numberBuilder)
        {
            var nextAtomicRomanNumber = atomicNumber.GetNextAtomicRomanNumber();
            var minusPart = nextAtomicRomanNumber.DecimalNumber - targetNumber;
            var minusRomanNumber = AtomicRomanNumberChar.ConvertAtomicDecimalNumberToRomanNumber(minusPart);
            numberBuilder.Append(minusRomanNumber).Append(nextAtomicRomanNumber.RomanNumberChar);
        }

        private static bool DuplicatedGreaterThanLimit(int count)
        {
            return count == MaxDuplicatedLimit;
        }

        private static void RemoveDuplicatedPreNumber(StringBuilder numberBuilder)
        {
            numberBuilder.Remove(numberBuilder.Length - MaxDuplicatedLimit, MaxDuplicatedLimit);
        }

        private static int TryToAppendNumberInMinusStyle(int decimalNumber, AtomicRomanNumberChar atomicNumber,
            StringBuilder numberBuilder, char atomicRomanNumberChar)
        {
            foreach (var legalMinusNumber in atomicNumber.GetLegalMinusAtomicRomanNumbersLessThanAtomicDecimalNumber())
            {
                var possibleNumberInMinusStyle = atomicNumber.DecimalNumber - legalMinusNumber.DecimalNumber;
                if (decimalNumber >= possibleNumberInMinusStyle)
                {
                    numberBuilder.Append(legalMinusNumber.RomanNumberChar).Append(atomicRomanNumberChar);
                    decimalNumber -= possibleNumberInMinusStyle;
                }
            }
            return decimalNumber;
        }
    }
}

